/**
 * Copyright (c) 2019, Freqchip
 *
 * All rights reserved.
 *
 *
 */

/*
 * INCLUDES
 */
#include <stdint.h>
#include <string.h>
#include <stdbool.h>

#include "nonos.h"
#include "lcd_st7789.h"
#include "lcd_font.h"

#define DEV_V_1_4  (1)  //FR8016H DEV1.4

#define LCD_DC_DATA() dev->dc(1)
#define LCD_DC_CMD()  dev->dc(0)

#define LCD_RES_LOW()  dev->rst(0)
#define LCD_RES_HIGH() dev->rst(1)

#define LCD_DriverDelay(x) mcu_delay_ms(x)

static _RAMCODE_ void LCD_WR_DATA8(driver_st7789_t *dev, uint8_t data) {
    LCD_DC_DATA();
    dev->spiWriteByte(data);
}

static _RAMCODE_ void LCD_WR_DATA(driver_st7789_t *dev, uint16_t data) {
    LCD_DC_DATA();
    dev->spiWriteByte(data>>8);
    dev->spiWriteByte(data);
}

static _RAMCODE_ void LCD_WR_REG(driver_st7789_t *dev, uint8_t cmd) {
    LCD_DC_CMD();
    dev->spiWriteByte(cmd);
}

/*********************************************************************
* @fn		LCD_Address_Set
* @brief	Write register
* @param	x1,x2 Set the start and end addresses of a row
*			y1,y2 Set the start and end addresses of a line
* @return	None.
*/
static void LCD_Address_Set(driver_st7789_t *dev, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
    if(USE_HORIZONTAL==0) {
        LCD_WR_REG(dev, 0x2a);//列地址设置
        LCD_WR_DATA(dev, x1);
        LCD_WR_DATA(dev, x2);
        LCD_WR_REG(dev, 0x2b);//行地址设置
        LCD_WR_DATA(dev, y1);
        LCD_WR_DATA(dev, y2);
        LCD_WR_REG(dev, 0x2c);//储存器写
    }
    else if(USE_HORIZONTAL==1) {
        LCD_WR_REG(dev, 0x2a);//列地址设置
        LCD_WR_DATA(dev, x1);
        LCD_WR_DATA(dev, x2);
        LCD_WR_REG(dev, 0x2b);//行地址设置
        LCD_WR_DATA(dev, y1+80);
        LCD_WR_DATA(dev, y2+80);
        LCD_WR_REG(dev, 0x2c);//储存器写
    }
    else if(USE_HORIZONTAL==2) {
        LCD_WR_REG(dev, 0x2a);//列地址设置
        LCD_WR_DATA(dev, x1);
        LCD_WR_DATA(dev, x2);
        LCD_WR_REG(dev, 0x2b);//行地址设置
        LCD_WR_DATA(dev, y1);
        LCD_WR_DATA(dev, y2);
        LCD_WR_REG(dev, 0x2c);//储存器写
    }
    else {
        LCD_WR_REG(dev, 0x2a);//列地址设置
        LCD_WR_DATA(dev, x1+80);
        LCD_WR_DATA(dev, x2+80);
        LCD_WR_REG(dev, 0x2b);//行地址设置
        LCD_WR_DATA(dev, y1);
        LCD_WR_DATA(dev, y2);
        LCD_WR_REG(dev, 0x2c);//储存器写
    }
}

/*********************************************************************
* @fn		st7789_init
* @brief	Initialize lcd
* @param	None
* @return	None.
*/
void st7789_init(driver_st7789_t *dev, uint16_t backColor, FUNC_GPIO_Out dc, FUNC_GPIO_Out rst, FUNC_spiWriteByte spiWriteByte, FUNC_spiWrite spiWrite) {
	dev->backColor = backColor;   // 背景色
	dev->dc = dc;
	dev->rst = rst;
	dev->spiWriteByte = spiWriteByte;
	dev->spiWrite = spiWrite;

    LCD_DC_CMD();

    LCD_RES_HIGH();
    LCD_DriverDelay(10);
    LCD_RES_LOW();
    LCD_DriverDelay(10);          //Delay 10ms
    LCD_RES_HIGH();
    LCD_DriverDelay(10);          //Delay 10ms

    LCD_WR_REG(dev, 0x11);
    LCD_DriverDelay(120);          //Delay 120ms

	LCD_WR_REG(dev, 0xfe);
	LCD_WR_REG(dev, 0xef);

	LCD_WR_REG(dev, 0x36);
	LCD_WR_DATA8(dev, 0x48);
	LCD_WR_REG(dev, 0x3a);
	LCD_WR_DATA8(dev, 0x05);

	LCD_WR_REG(dev, 0x85);
	LCD_WR_DATA8(dev, 0x80);
	LCD_WR_REG(dev, 0x86);
	LCD_WR_DATA8(dev, 0xf8);
	LCD_WR_REG(dev, 0x87);
	LCD_WR_DATA8(dev, 0x79);
	LCD_WR_REG(dev, 0x89);
	LCD_WR_DATA8(dev, 0x13);

	LCD_WR_REG(dev, 0x8b);
	LCD_WR_DATA8(dev, 0x80);

	LCD_WR_REG(dev, 0x8d);
	LCD_WR_DATA8(dev, 0x33);
	LCD_WR_REG(dev, 0x8e);
	LCD_WR_DATA8(dev, 0x8f);
	LCD_WR_REG(dev, 0x8f);
	LCD_WR_DATA8(dev, 0x73);

	//inversion
	LCD_WR_REG(dev, 0xe8);
	LCD_WR_DATA8(dev, 0x12);
	LCD_WR_DATA8(dev, 0x00);

	LCD_WR_REG(dev, 0xec);
	LCD_WR_DATA8(dev, 0x13);
	LCD_WR_DATA8(dev, 0x02);
	LCD_WR_DATA8(dev, 0x88);

	//cobD?×?
	LCD_WR_REG(dev, 0xbf);
	LCD_WR_DATA8(dev, 0x1c);

	LCD_WR_REG(dev, 0xad);
	LCD_WR_DATA8(dev, 0x4a);

	LCD_WR_REG(dev, 0xae);
	LCD_WR_DATA8(dev, 0x44);

	LCD_WR_REG(dev, 0xac);
	LCD_WR_DATA8(dev, 0x44);

	LCD_WR_REG(dev, 0xff);
	LCD_WR_DATA8(dev, 0x62);
	LCD_WR_REG(dev, 0x99);
	LCD_WR_DATA8(dev, 0x3e);
	LCD_WR_REG(dev, 0x9d);
	LCD_WR_DATA8(dev, 0x4b);
	LCD_WR_REG(dev, 0x98);
	LCD_WR_DATA8(dev, 0x3e);
	LCD_WR_REG(dev, 0x9c);
	LCD_WR_DATA8(dev, 0x4b);

	LCD_WR_REG(dev, 0xc3);
	LCD_WR_DATA8(dev, 0x58);//58

	LCD_WR_REG(dev, 0xc4);
	LCD_WR_DATA8(dev, 0x4E);//4E

	LCD_WR_REG(dev, 0xc9);
	LCD_WR_DATA8(dev, 0x08);

	LCD_WR_REG(dev, 0xf0);
	LCD_WR_DATA8(dev, 0x45);
	LCD_WR_DATA8(dev, 0x0A);
	LCD_WR_DATA8(dev, 0x0A);
	LCD_WR_DATA8(dev, 0x06);
	LCD_WR_DATA8(dev, 0x05);
	LCD_WR_DATA8(dev, 0x2E);

	LCD_WR_REG(dev, 0xf2);
	LCD_WR_DATA8(dev, 0x45);
	LCD_WR_DATA8(dev, 0x09);
	LCD_WR_DATA8(dev, 0x0A);
	LCD_WR_DATA8(dev, 0x0b);
	LCD_WR_DATA8(dev, 0x05);
	LCD_WR_DATA8(dev, 0x2E);

	LCD_WR_REG(dev, 0xf1);
	LCD_WR_DATA8(dev, 0x45);
	LCD_WR_DATA8(dev, 0x8F);
	LCD_WR_DATA8(dev, 0x8f);
	LCD_WR_DATA8(dev, 0x3B);
	LCD_WR_DATA8(dev, 0x3F);
	LCD_WR_DATA8(dev, 0x7f);

	LCD_WR_REG(dev, 0xf3);
	LCD_WR_DATA8(dev, 0x45);
	LCD_WR_DATA8(dev, 0x8f);
	LCD_WR_DATA8(dev, 0x8f);
	LCD_WR_DATA8(dev, 0x3B);
	LCD_WR_DATA8(dev, 0x3F);
	LCD_WR_DATA8(dev, 0x7f);

	LCD_WR_REG(dev, 0x11);
	LCD_DriverDelay(120);
	LCD_WR_REG(dev, 0x29);
	LCD_WR_REG(dev, 0x2c);
	
	//st7789_clear(dev, LCD_RED);   LCD_DriverDelay(300);
	//st7789_clear(dev, LCD_GREEN); LCD_DriverDelay(300);
	//st7789_clear(dev, LCD_BLUE);  LCD_DriverDelay(300);	
	st7789_clear(dev, dev->backColor);
}

/*********************************************************************
* @fn		st7789_clear
* @brief	clear screen
* @param	color   - color after clearing screen
* @return	None.
*/
void st7789_clear0(driver_st7789_t *dev, uint16_t color) {
	uint16_t i, j;
	LCD_Address_Set(dev, 0, 0, LCD_W-1, LCD_H-1);
	for(i = 0; i < LCD_W; i++) {
		for(j = 0; j < LCD_H; j++) {
			LCD_WR_DATA(dev, color);
		}
	}
}

void st7789_clear(driver_st7789_t *dev, uint16_t color) {
	uint16_t i;
    uint16_t st7789_buf[LCD_W];
    LCD_Address_Set(dev, 0, 0, LCD_W-1, LCD_H-1);	
	// 填充一行背景色
	color = (color << 8) | (color >> 8);
	for(i = 0; i < LCD_W; i++) { st7789_buf[i] = color; }
	// 逐行清除
    for(i = 0; i < LCD_H; i++) { dev->spiWrite((uint8_t *)st7789_buf, 480); }
}

/*********************************************************************
* @fn		st7789_showChinese
* @brief	Display Chinese Characters
* @param	x,y	  - Starting coordinates
*			index - Chinese Character Number
*           size  - Font size
*           color - color
* @return	None.
*/
void st7789_showChinese(driver_st7789_t *dev, uint16_t x, uint16_t y, uint8_t index, uint8_t size, uint16_t color) {
    uint8_t i,j;
    uint8_t *hzk, size1;
	
	// 选择字号
	hzk = (size == 16) ? Hzk16 : Hzk32;
	
	// 设置一个汉字的区域
    LCD_Address_Set(dev, x, y, x+size-1, y+size-1); 
	// 一个汉字所占的字节
    size1 = (size * size) / 8;	
	// 写入的起始位置
    hzk += index * size1;
    for(j=0; j<size1; j++) {
        for(i=0; i<8; i++) {
			// 从数据的低位开始写
            if((*hzk & (1 << i)) != 0) {
                LCD_WR_DATA(dev, color); // 点亮
            } else {
                LCD_WR_DATA(dev, dev->backColor); // 不点亮
            }
        }
        hzk++;
    }
}

/*********************************************************************
* @fn		st7789_drawPoint
* @brief	Draw points
* @param	x,y	  - Starting coordinates
*           color - color
* @return	None.
*/
void st7789_drawPoint(driver_st7789_t *dev, uint16_t x, uint16_t y, uint16_t color) {
	// 设置光标位置
    LCD_Address_Set(dev, x, y, x, y);
    LCD_WR_DATA(dev, color);
}

/*********************************************************************
* @fn		st7789_fill
* @brief	Full screen fill
* @param	xsta,xend Set the start and end addresses of a row
*			ysta,yend Set the start and end addresses of a line
*           color - fill color
* @return	None.
*/
void st7789_fill(driver_st7789_t *dev, uint16_t xsta, uint16_t ysta, uint16_t xend, uint16_t yend, uint16_t color) {
    uint16_t i,j;
	// 设置光标位置
    LCD_Address_Set(dev, xsta, ysta, xend, yend);      
    for(i=ysta; i<=yend; i++) {
        for(j = xsta; j <= xend; j++) { LCD_WR_DATA(dev, color); }
    }
}

/*********************************************************************
* @fn		st7789_drawLine
* @brief	Draw a straight line
* @param	x1,x2 Set the start and end addresses of a row
*			y1,y2 Set the start and end addresses of a line
*           color -  color
* @return	None.
*/
void st7789_drawLine(driver_st7789_t *dev, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) {
    uint16_t t;
    int xerr = 0, yerr = 0, delta_x, delta_y, distance;
    int incx, incy, uRow, uCol;
    delta_x = x2 - x1; delta_y = y2 - y1; // 计算坐标增量
    uRow = x1; uCol = y1;                 // 画线起点坐标

    if(delta_x > 0) { incx = 1; }         // 设置单步方向
    else if(delta_x == 0) { incx = 0; }   // 垂直线
    else { incx = -1; delta_x = -delta_x; }

    if(delta_y > 0) { incy = 1; }
    else if(delta_y == 0) { incy = 0; }     // 水平线
    else { incy = -1; delta_y = -delta_x; }

    if(delta_x > delta_y) { distance = delta_x; } // 选取基本增量坐标轴
    else { distance = delta_y; }

    for(t=0; t < distance+1; t++) {
        st7789_drawPoint(dev, uRow, uCol, color); // 画点
        xerr += delta_x;
        yerr += delta_y;
        if(xerr > distance) {
            xerr -= distance;
            uRow += incx;
        }
        if(yerr > distance) {
            yerr -= distance;
            uCol += incy;
        }
    }
}

/*********************************************************************
* @fn		st7789_drawRectangle
* @brief	Draw a rectangle
* @param	x1,x2 Set the start and end addresses of a row
*			y1,y2 Set the start and end addresses of a line
*           color -  color
* @return	None.
*/
void st7789_drawRectangle(driver_st7789_t *dev, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,uint16_t color) {
    st7789_drawLine(dev, x1, y1, x2, y1, color);
    st7789_drawLine(dev, x1, y1, x1, y2, color);
    st7789_drawLine(dev, x1, y2, x2, y2, color);
    st7789_drawLine(dev, x2, y1, x2, y2, color);
}

/*********************************************************************
* @fn		st7789_drawCircle
* @brief	Draw a Circle
* @param	x0,y0 - Set the center point
*			r     - Circle radius
*			color -  color
* @return	None.
*/
void st7789_drawCircle(driver_st7789_t *dev, uint16_t x0, uint16_t y0, uint8_t r, uint16_t color) {
    int a = 0;
    int b = r;

    while(a <= b) {
        st7789_drawPoint(dev, x0-b, y0-a, color); //3
        st7789_drawPoint(dev, x0+b, y0-a, color); //0
        st7789_drawPoint(dev, x0-a, y0+b, color); //1
        st7789_drawPoint(dev, x0-a, y0-b, color); //2
        st7789_drawPoint(dev, x0+b, y0+a, color); //4
        st7789_drawPoint(dev, x0+a, y0-b, color); //5
        st7789_drawPoint(dev, x0+a, y0+b, color); //6
        st7789_drawPoint(dev, x0-b, y0+a, color); //7
        a++;
		//判断要画的点是否过远
        if((a*a + b*b) > (r*r)) { b--; }
    }
}

/*********************************************************************
* @fn		st7789_showChar
* @brief	Display character
* @param	x,y - Set the Starting coordinates
*			ch	  - ASCII value of character
*			mode  - 1: Overlay 0: Non-overlapping
*           color - color
* @return	None.
*/
void st7789_showChar(driver_st7789_t *dev, uint16_t x, uint16_t y, char ch, uint8_t mode, uint16_t color) {
    uint8_t temp;
    uint8_t pos,t;
    uint16_t x0=x;

    if(x > LCD_W-16 || y > LCD_H-16) { return; }
    ch = ch - ' '; // 得到偏移后的值

    LCD_Address_Set(dev, x, y, x+8-1, y+16-1);      // 设置光标位置

    if(!mode) {
		// 非叠加方式
        for(pos=0; pos<16; pos++) {
            temp = asc2_1608[(uint16_t)ch*16 + pos];        // 调用1608字体
            for(t=0; t<8; t++) {
                if(temp & 0x01) { LCD_WR_DATA(dev, color); }
                else { LCD_WR_DATA(dev, dev->backColor); }
                temp >>= 1;
                x++;
            }
            x=x0;
            y++;
        }
    } else {
		// 叠加方式
        for(pos=0; pos<16; pos++) {
            temp = asc2_1608[(uint16_t)ch*16 + pos];        // 调用1608字体
            for(t=0; t<8; t++) {
                if(temp & 0x01) { st7789_drawPoint(dev, x+t, y+pos, color); }//画一个点
                temp >>= 1;
            }
        }
    }
}

/*********************************************************************
* @fn		st7789_showString
* @brief	Display string
* @param	x,y - Set the Starting coordinates
*			str - String pointer
*			color - color
* @return	None.
*/
void st7789_showString(driver_st7789_t *dev, uint16_t x, uint16_t y, const char *str, uint16_t color) {
    while(*str != '\0') {
        if(x > LCD_W-16) {
            x = 0;
            y += 16;
        }
        if(y > LCD_H-16) {
            y = x = 0;
            st7789_clear(dev, dev->backColor);
        }
        st7789_showChar(dev, x, y, *str, 0, color);
        x += 8;
        str++;
    }
}

/*********************************************************************
* @fn		st7789_showPicture
* @brief	Display a picture with a resolution of 240x240
* @param	pic - The picture data
* @return	None.
*/
void st7789_showPicture(driver_st7789_t *dev, uint8_t *pic) {
	LCD_Address_Set(dev, 0, 0, 240-1, 240-1);
	dev->spiWrite(pic, 480*240);
}
